////////////////////////////////////////////////////////////////////////////////
//                                                                            //
//  Filename:    ParisBeautyGuts.cpp                                          //
//  Developer:   Phillip Sharpless                                            //
//  Create date: 06/05/2006                                                   //
//                                                                            //
////////////////////////////////////////////////////////////////////////////////
//                                                                            //
//  Description: This is the class implementation for the slot gut of the     //
//               Australian Slot machine called Paris Beauty                  //
//                                                                            //
////////////////////////////////////////////////////////////////////////////////
//        Copyright (c) 2006 - Realtime Gaming.  All rights reserved.         //
////////////////////////////////////////////////////////////////////////////////

#include "ParisBeautyGuts.h"
#include <math.h>

////////////////////////////////////////////////////////////////////////////
/// ParisBeautyGuts::m_aLineWinSpinResults
///
/// This array is used to quickly get the SpinResult for the number of
/// icons.  It is indexed by wheel icon and then count.  So to get the
/// SpinResult for 5 Breads, use the following code snippet:
///
///		SpinResult result = m_aLineWinSpinResults[ ICON_BREAD ][ 5 ];
///
/// In this example result would be set to EVT_FIVE_BREAD.
////////////////////////////////////////////////////////////////////////////
ParisBeautyGuts::SpinResult ParisBeautyGuts::m_aLineWinSpinResults[][6] = 
{
// Number of icons:				0			1			2						3						4						5
/* ICON_NINE */				{	EVT_LOSER,	EVT_LOSER,	EVT_LOSER,				EVT_THREE_NINE,			EVT_FOUR_NINE,			EVT_FIVE_NINE			},
/* ICON_TEN */				{	EVT_LOSER,	EVT_LOSER,	EVT_LOSER,				EVT_THREE_TEN,			EVT_FOUR_TEN,			EVT_FIVE_TEN			},
/* ICON_JACK */				{	EVT_LOSER,	EVT_LOSER,	EVT_LOSER,				EVT_THREE_JACK,			EVT_FOUR_JACK,			EVT_FIVE_JACK			},
/* ICON_QUEEN */			{	EVT_LOSER,	EVT_LOSER,	EVT_LOSER,				EVT_THREE_QUEEN,		EVT_FOUR_QUEEN,			EVT_FIVE_QUEEN			},
/* ICON_KING */				{	EVT_LOSER,	EVT_LOSER,	EVT_LOSER,				EVT_THREE_KING,			EVT_FOUR_KING,			EVT_FIVE_KING			},
/* ICON_ACE */				{	EVT_LOSER,	EVT_LOSER,	EVT_LOSER,				EVT_THREE_ACE,			EVT_FOUR_ACE,			EVT_FIVE_ACE			},
/* ICON_BREAD */			{	EVT_LOSER,	EVT_LOSER,	EVT_TWO_BREAD,			EVT_THREE_BREAD,		EVT_FOUR_BREAD,			EVT_FIVE_BREAD			},
/* ICON_PALETTE */			{	EVT_LOSER,	EVT_LOSER,	EVT_TWO_PALETTE,		EVT_THREE_PALETTE,		EVT_FOUR_PALETTE,		EVT_FIVE_PALETTE		},
/* ICON_ARCDETRIUMPH */		{	EVT_LOSER,	EVT_LOSER,	EVT_TWO_ARCDETRIUMPH,	EVT_THREE_ARCDETRIUMPH,	EVT_FOUR_ARCDETRIUMPH,	EVT_FIVE_ARCDETRIUMPH	},
/* ICON_FRENCHMAN */		{	EVT_LOSER,	EVT_LOSER,	EVT_TWO_FRENCHMAN,		EVT_THREE_FRENCHMAN,	EVT_FOUR_FRENCHMAN,		EVT_FIVE_FRENCHMAN		},
/* ICON_EIFFEL_TOWER */		{	EVT_LOSER,	EVT_LOSER,	EVT_LOSER,				EVT_LOSER,				EVT_LOSER,				EVT_LOSER				},
/* ICON_LADY */				{	EVT_LOSER,	EVT_LOSER,	EVT_LOSER,				EVT_LOSER,				EVT_LOSER,				EVT_LOSER				} 
};

////////////////////////////////////////////////////////////////////////////
/// ParisBeautyGuts::m_aScatterWinSpinResults
///
/// This array is used to quickly get the SpinResult for the number of
/// icons.  It is indexed by icon count only, since Eiffel Towers are the only
/// scatter win.  So to get the SpinResult for 5 Eiffel Towers, use the
/// following code snippet:
///
///		SpinResult result = m_aScatterWinSpinResults[ 5 ];
///
/// In this example result would be set to EVT_FIVE_EIFFEL_TOWER.
////////////////////////////////////////////////////////////////////////////
ParisBeautyGuts::SpinResult ParisBeautyGuts::m_aScatterWinSpinResults[6] = 
{
	// ICON_EIFFEL_TOWER		Number of Icons
	EVT_LOSER,					// 0
	EVT_SCATTER_NEAR_MISS,		// 1
	EVT_TWO_EIFFEL_TOWER,		// 2
	EVT_THREE_EIFFEL_TOWER,		// 3
	EVT_FOUR_EIFFEL_TOWER,		// 4
	EVT_FIVE_EIFFEL_TOWER		// 5
};

////////////////////////////////////////////////////////////////////////////
/// @fn 	 ParisBeautyGuts::ParisBeautyGuts()   
///
/// This is the default constructor for the class.
////////////////////////////////////////////////////////////////////////////
ParisBeautyGuts::ParisBeautyGuts()
{
	// If this doesn't compile it means that someone probably added
	// another icon type but did not update the
	// m_aLineWinSpinResults array. 
	C_ASSERT( countof( m_aLineWinSpinResults ) == NUM_VALID_ICONS );

}

////////////////////////////////////////////////////////////////////////////
/// @fn 	 ParisBeautyGuts::~ParisBeautyGuts()
///
/// Destructor for ParisBeautyGuts. 
////////////////////////////////////////////////////////////////////////////
ParisBeautyGuts::~ParisBeautyGuts()
{
}

////////////////////////////////////////////////////////////////////////////
/// @fn 	 ParisBeautyGuts::classifyStops()   
///
/// @param   stops      The array of stop positions for the line.
/// @param   bet        The value of the line bet.
/// @param   line       The index of the line.  
///
/// This helper method is used to calculate the payout on a line.
////////////////////////////////////////////////////////////////////////////
void ParisBeautyGuts::classifyStops( SpinLine* spinline, uint8* stops, uint8 bet, uint8 line,
	uint8& rtnresult, uint8& multiplier )
{
	//no payline payouts are made after a respin
	if(getGameModeId() != MODE_RESPIN)
	{
		// Start with the icon on the first reel of the payline
		uint8 testIcon = getIcon( getGameModeId(), 0, stops[0] )->iconid;

		// Declare a local variable to track whether or not we found a substitution.
		bool bSubstitution = false;

		// See what the spin result is if we used any Ladys as substitution symbols.
		rtnresult = getLineWinResultWithPossibleSubstitution( stops, testIcon, bSubstitution );

		// If we did actually have a substitution and a winning spin...
		if (bSubstitution && rtnresult != EVT_LOSER )
		{
			// then add a note and change the multiplier.
			spinline->addNote( NOTE_LADY_SUBSTITUTION );
			multiplier = MULTIPLIER_LADY_SUBSTITUTION;
		}
	}
}

////////////////////////////////////////////////////////////////////////////
/// @fn 	 ParisBeautyGuts::SpinResult ParisBeautyGuts::getLineWinResultWithPossibleSubstitution( uint8* stops, uint8 testIcon, bool& bSubstitution ) const
///
/// @param   stops			The array of stop positions for the line.
/// @param   testIcon		The icon we are looking for, typically the first icon on the line.
/// @param   bSubstitution	An output parameter that is set if a subsitution is used.
///
/// @return	 SpinResult				A payout from the SpinResult enumeration.
///
/// This helper method is used to calculate the line win payout, including possible substitution.
////////////////////////////////////////////////////////////////////////////
ParisBeautyGuts::SpinResult ParisBeautyGuts::getLineWinResultWithPossibleSubstitution(
	uint8* stops, uint8 testIcon, bool& bSubstitution ) const
{
	// Initialize the out parameter
	bSubstitution = false;

	// Loop through the remaining reels until the streak is over
	for ( int i = 1; i < getReelCount(); i++ )
    {
		// See what this icon is.
		uint8 currentIcon = getIcon( getGameModeId(), i, stops[i] )->iconid;

		// If the current icon is a Lady
		if ( currentIcon == ICON_LADY )
		{
			// then use it as a substitution symbol and continue on.
			bSubstitution = true;
			continue;
		}
		// Otherwise if the test icon is a Lady
		else if ( testIcon == ICON_LADY )
		{
			// then we still have a substitution.  In this case the line
			// started out with the substitution symbol.
			bSubstitution = true;

			// We have now found our first icon that is not a substitution symbol.
			// All of the rest of the icons will have to match this one now for the
			// streak to continue, so change our test icon.
			testIcon = currentIcon;
			continue;
		}

		// If the current icon is not the test icon...
        if ( currentIcon != testIcon )
        {
			// then the streak is over so break out of the loop
			break;
        }
    }

	// Use the array to look up the spin result for i testIcons.
	return lookupLineWinSpinResult( testIcon, i );
}

////////////////////////////////////////////////////////////////////////////
/// @fn 	 ParisBeautyGuts::classifyScatters( SlotSpin* spin )
///
/// @param   spin       Pointer to the spin with stop data.
///
/// This is an override of the framework method.  
/// It determines if the current spin merits free spins or scatter payments.
////////////////////////////////////////////////////////////////////////////
void ParisBeautyGuts::classifyScatters( SlotSpin* spin )
{	
	// Try for the Random Progressive Jackpot
	tryForJackpot(spin);

	int32 nScatterPositions = 0;

	SpinResult srResult = getScatterWinResult(spin, nScatterPositions);

	// Add the scatter, if any.
	evaluateScatterWin(spin, srResult, nScatterPositions);

	// See if we triggered the free game feature.
	evaluateFeatureTrigger( spin, srResult, nScatterPositions );
}

////////////////////////////////////////////////////////////////////////////
/// @fn		ParisBeautyGuts::tryForJackpot()
///
///	This function tries to win the jackpot and adds the scatter if we hit
/// the jackpot.
////////////////////////////////////////////////////////////////////////////
void ParisBeautyGuts::tryForJackpot( SlotSpin* spin )
{
	// If we are in the first paid spin (NOT a bonus spin)...
	if ( getGameModeId() == MODE_NORMAL ) 
	{
		// then try for the Random Progressive Jackpot
		// If we hit the jackpot...
		if ( getRandomProgressiveJackpotResult( spin->getSpinLineCount() ) || 
			m_bForceRandomJackpot)
		{
			// then add a jackpot scatter to the spin.
			spin->addScatter( 0, getPayout( EVT_RANDOM_PROGRESSIVE_WINNER )->payoutAmount, 1,
				true, EVT_RANDOM_PROGRESSIVE_WINNER, 0 );
		}
    }
}

////////////////////////////////////////////////////////////////////////////
/// @fn		ParisBeautyGuts::getScatterWinResult()
///
/// @param   spin				The spin we are evaluating.
/// @param   scatterPositions	An output parameter that contains a bit mask for the icon positions.
///
/// @return	 SpinResult			A payout from the SpinResult enumeration.
///
///	This function looks for scatter wins.
////////////////////////////////////////////////////////////////////////////
ParisBeautyGuts::SpinResult ParisBeautyGuts::getScatterWinResult( SlotSpin* spin, int32& scatterPositions) const
{
	// Keep up with how many scatter icons we found.
	uint8 numScatterIcons = 0;

	// Loop through each reel.
  	for( int reel = 0; reel < getReelCount(); reel++ )
	{
		// The position bit for the current icon in case it needs to be stored in a mask.
		uint8 position = reel;

		// Keep up with whether or not we found a scatter icon on this reel.
		bool bFoundScatterIconOnReel = false;

		// Loop through each row in the wheel house.
		for( int row = 0; row < 3; row++ )
		{
			// Get the current stop.
			uint8 stop = relativeWheelPosition( getGameModeId(), reel, spin->getStops()[ reel ], row );

			// Get the icon for the current stop.
			uint8 currentIcon = getIcon( getGameModeId(), reel, stop )->iconid;
			
			// If the icon is an Eiffel Tower...
			if ( currentIcon == ICON_EIFFEL_TOWER )
			{
				// Set the flag so that we know we found a scatter icon on this reel.
				bFoundScatterIconOnReel = true;
			}

			// If we found a scatter icon...
			if ( bFoundScatterIconOnReel )
			{
				// then increase the scatter icon count
				numScatterIcons++;

				// Set the bit in the position mask for this stop.
				scatterPositions |= 1 << position;

				// Break out now.
				break;
			}

			// Move to the next bit in the position mask.
            position += 5;
		}
	}

	// Use the array to look up the spin result in the current game mode.
	return lookupScatterWinSpinResult( numScatterIcons );
}

////////////////////////////////////////////////////////////////////////////
/// @fn 	 ParisBeautyGuts::evaluateScatterWin()
///
/// This function determines if the current spin wins a scatter payment and
/// adds the scatter.
////////////////////////////////////////////////////////////////////////////
void ParisBeautyGuts::evaluateScatterWin( SlotSpin* spin, SpinResult srScatterResult, int32 scatterPositions)
{
	// If we got the scatter win (or almost got it)
	if ( srScatterResult != EVT_LOSER )
	{
		// Get the base payout for the scatter.
		int32 nPayout = getPayout( srScatterResult )->payoutAmount;
		
		// Scatters payout based on the total bet, so factor in the number of bet lines.
		nPayout *= spin->getSpinLineCount();

		// Default to no multiplier.
		int32 nMultiplier = 1;

		// Freespin and Respin mode have a multiplier
		if(getGameModeId() == MODE_FREE_SPIN || getGameModeId() == MODE_RESPIN)
			nMultiplier = MULTIPLIER_FREE_SPIN;

		// Add the payout to the total win for this spin.
		increaseTotalWin( nPayout * nMultiplier );
    
		// Add a scatter to the spin.
		spin->addScatter( scatterPositions, nPayout * getBetPerLine(), nMultiplier, 
						  false, srScatterResult, 0 );
	}
}

////////////////////////////////////////////////////////////////////////////
/// @fn 	 ParisBeautyGuts::evaluateFeatureTrigger()
///
/// This function determines if the current spin triggers the free game
///	feature and adds the scatter.
////////////////////////////////////////////////////////////////////////////
void ParisBeautyGuts::evaluateFeatureTrigger( SlotSpin* spin, SpinResult srScatterResult, int32 scatterPositions )
{
	// If we are in re-spin mode...
	if ( getGameModeId() == MODE_RESPIN )
	{
		// then see if they triggered the additional free games feature.
		if ( srScatterResult == EVT_THREE_EIFFEL_TOWER ||
			 srScatterResult == EVT_FOUR_EIFFEL_TOWER ||
			 srScatterResult == EVT_FIVE_EIFFEL_TOWER)
		{
			// Make sure we are in free spin mode.
			setGameModeId( MODE_FREE_SPIN );

			// They are awarded 12 additional free spins
			int32 nFreeSpins = 12;

			// Add the free spins.
			incrementFreeSpins( nFreeSpins );

			// Add a scatter to the spin for the feature trigger.
			spin->addScatter( scatterPositions, 0, 0, false, EVT_FREE_GAME_FEATURE, nFreeSpins );
		}
		// Otherwise...
		else
		{
			// if we still will have free spins after this re-spin...
			if ( getFreeSpins() > 1 )
			{
				// then switch back to free spin mode so that we don't play the
				// remaining free spins as re-spins.
				setGameModeId( MODE_FREE_SPIN );
			}
		}
	}
	// If we are in free spin mode...
	else if( getGameModeId() == MODE_FREE_SPIN )
	{
		// see if they triggered the re-spin feature.
		if ( srScatterResult == EVT_TWO_EIFFEL_TOWER ||
			 srScatterResult == EVT_THREE_EIFFEL_TOWER ||
			 srScatterResult == EVT_FOUR_EIFFEL_TOWER)
		{
			// They triggered the re-spin feature.
			// Make sure we are in re-spin mode.
			setGameModeId( MODE_RESPIN );

			// Add the re-spins.
			incrementFreeSpins( 1 );

			// We need to see which reels had scatters on them.
			// This array of masks will extract the scatter position
			// bits for the given reel.  So to see if reel 2 had any
			// scatters on it, just do this:
			//   scatterPositions & nScatterMask[ 2 ]
			int32 nScatterMask[] = 
			{
				SCATTER_REEL_0_MASK,
				SCATTER_REEL_1_MASK,
				SCATTER_REEL_2_MASK,
				SCATTER_REEL_3_MASK,
				SCATTER_REEL_4_MASK
			};

			// Make sure we have an entry in the scatter mask array
			// for each reel in the game.
			ASSERT( countof( nScatterMask ) == getReelCount() );

			// Get the stops for the spin.
			const uint8* pStops = spin->getStops();

			int nReelsHeld = 0;

			// Loop through all of the reels.
			for ( uint8 n = 0; n < getReelCount(); n++ )
			{
				// If this reel has a scatter icon on it...
				if ( scatterPositions & nScatterMask[ n ] )
				{
					// Then set the hold stop to be the current stop for the reel.
					setReelHoldStop( n, pStops[ n ] );

					nReelsHeld++;
				}
				else
				{
					// No reel hold
					setReelHoldStop( n, -1);
				}
			}

			// Make sure that we found at least 2 reels that are held.
			ASSERT( nReelsHeld >= 2 );

			// Sanity check.
			ASSERT( nReelsHeld <= getReelCount() );

			// Add a scatter to the spin for the feature trigger.
			spin->addScatter( scatterPositions, 0, 0, false, EVT_RESPIN_FEATURE, 1 );
		}
	}
	// if we are in normal mode...
	else if( getGameModeId() == MODE_NORMAL )
	{
		// then see if they triggered the free games feature.
		if ( srScatterResult == EVT_THREE_EIFFEL_TOWER ||
			 srScatterResult == EVT_FOUR_EIFFEL_TOWER ||
			 srScatterResult == EVT_FIVE_EIFFEL_TOWER)
		{
			setGameModeId( MODE_FREE_SPIN );

			// They are awarded 12 free spins
			int32 nFreeSpins = 12;

			// Add the free spins.
			incrementFreeSpins( nFreeSpins );

			// Add a scatter to the spin for the feature trigger.
			spin->addScatter( scatterPositions, 0, 0, false, EVT_FREE_GAME_FEATURE, nFreeSpins );
		}
	}
}

////////////////////////////////////////////////////////////////////////////
/// @fn 	 ParisBeautyGuts::isValidGuts()   
///
/// @return	 bool8      True if gut is valid.    
///
/// This helper method is used to see if the current gut ID is valid.
////////////////////////////////////////////////////////////////////////////
bool8 ParisBeautyGuts::isValidGuts()
{
	// Make sure that the m_aLineWinSpinResults array has an entry for each reel,
	// and an additional entry in case there are 0 icons on the line.
	// Note:  we can't do this in the constructor because we have to call
	// getReelCount.
	ASSERT( countof( m_aLineWinSpinResults[0] ) == getReelCount( MODE_NORMAL ) + 1 );

	// Make sure that the m_aScatterWinSpinResults array has an entry for each reel,
	// and an additional entry in case there are 0 icons on the line.
	ASSERT( countof( m_aScatterWinSpinResults ) == getReelCount( MODE_NORMAL ) + 1 );

	// Now make sure that both game modes have the same number of reels.
	ASSERT( getReelCount( MODE_NORMAL ) == getReelCount( MODE_FREE_SPIN ) );

	// Finally just check to see if the gut is within the right range.
    return ( getGutsId() >= 3800 && getGutsId() < 3900 );
}

////////////////////////////////////////////////////////////////////////////
/// @fn 	 ParisBeautyGuts::getGameModeMultiplierAndNoteId( uint8& nGameModeMultiplier, uint8& nNoteId )
///
/// @param   nGameModeMultiplier	The multiplier for the game mode.
/// @param   nNoteId				The note ID for the game mode.
///
/// This helper method is used to get the game mode multiplier and note ID.
////////////////////////////////////////////////////////////////////////////
void ParisBeautyGuts::getGameModeMultiplierAndNoteId( uint8& nGameModeMultiplier,
	uint8& nNoteId )
{
	// If we are in free spin mode...
    if ( getGameModeId() == MODE_FREE_SPIN || getGameModeId() == MODE_RESPIN)
    {
		//free spin note/multiplier
        nNoteId = NOTE_FREE_SPIN;
		nGameModeMultiplier = MULTIPLIER_FREE_SPIN;
    }
	// Otherwise...
    else
    {
		// just defer to the base class.
        SlotGuts::getGameModeMultiplierAndNoteId(nGameModeMultiplier, nNoteId );
    }
}

////////////////////////////////////////////////////////////////////////////
/// @fn 	 ParisBeautyGuts::getReelHoldStop()
///
/// Accessor function to get the stop position to hold for the passed in
/// reel.
////////////////////////////////////////////////////////////////////////////
int32 ParisBeautyGuts::getReelHoldStop( uint8 nReel )
{
	// Simple bounds check.
	ASSERT( nReel < getReelCount() );

	// Just return the sub-game data for the reel.
    return m_subGame->getData( SGDIDX_REEL_HOLD_STOP_1 + nReel );
}

////////////////////////////////////////////////////////////////////////////
/// @fn 	 ParisBeautyGuts::setReelHoldStop()
///
/// Accessor function to set the stop position to hold for the passed in
/// reel.
////////////////////////////////////////////////////////////////////////////
void ParisBeautyGuts::setReelHoldStop( uint8 nReel, int32 nStop )
{
	// Simple bounds check.
	ASSERT( nReel < getReelCount() );

	// Update the sub-game data.
    m_subGame->update( SGDIDX_REEL_HOLD_STOP_1 + nReel, nStop );

	// If we are holding this reel...
	if ( nStop != -1 )
	{
		// then log it in the feature data for an audit trail.
		m_feature->update( SGDIDX_REEL_HOLD_STOP_1 + nReel, nStop, true );
	}
}

////////////////////////////////////////////////////////////////////////////
/// @fn 	 ParisBeautyGuts::resetReelHoldStops()
///
/// This function resets all of the reel hold stops in preparation for a
/// new spin.
////////////////////////////////////////////////////////////////////////////
void ParisBeautyGuts::resetReelHoldStops()
{
	// Just set the sub-game data back to -1.
    m_subGame->update( SGDIDX_REEL_HOLD_STOP_1, -1 );
    m_subGame->update( SGDIDX_REEL_HOLD_STOP_2, -1 );
    m_subGame->update( SGDIDX_REEL_HOLD_STOP_3, -1 );
    m_subGame->update( SGDIDX_REEL_HOLD_STOP_4, -1 );
    m_subGame->update( SGDIDX_REEL_HOLD_STOP_5, -1 );
}

////////////////////////////////////////////////////////////////////////////
/// @fn 	 ParisBeautyGuts::preSpinClassifyResults( SlotSpin* spin )
///
/// @param   spin       Pointer to the spin with stop data.
///
/// This method is overridden in debug spins mode to alter the reel stops.
////////////////////////////////////////////////////////////////////////////
void ParisBeautyGuts::preSpinClassifyResults( SlotSpin* spin )
{
	SlotGuts::preSpinClassifyResults(spin);

	// If we are in the re-spin feature...
    if ( getGameModeId() == MODE_RESPIN )
    {
		// then we will need to hold some of the reel stops, which
		// means we need to cast away the constness of the stops.
        uint8* pStops = const_cast< uint8* >( spin->getStops() );
		
		uint8 nReelsHeld = 0;

		// Loop through all of the reels.
		for ( uint8 n = 0; n < getReelCount(); n++ )
		{
			// Get the stop we are supposed to use for this reel, if any.
			int32 nStop = getReelHoldStop( n );

			// If we are holding this reel at a specific stop...
			if ( nStop != -1 )
			{
				// then store the stop in the spin.
				pStops[ n ] = nStop;
				nReelsHeld++;
			}
		}

		// Make sure that we found at least 2 reels that are held.
		ASSERT( nReelsHeld >= 2 );

		// Sanity check.
		ASSERT( nReelsHeld <= getReelCount() );
    }

	return;
}

////////////////////////////////////////////////////////////////////////////
/// @fn 	 ParisBeautyGuts::Debug_SetLoserStops( uint8* pStops )
///
/// @param   pStops		The stops to set.
///
/// Base class override, returns a losing spin triggered by the debug tool
////////////////////////////////////////////////////////////////////////////
void ParisBeautyGuts::Debug_SetLoserStops( uint8* pStops )
{
	pStops[0]=0;
	pStops[1]=3;
	pStops[2]=7;
	pStops[3]=0;
	pStops[4]=0;
}